package com.youlu.campus.service.vote;

import com.mongodb.client.result.UpdateResult;
import com.youlu.campus.entity.UserActivityPointDetail;
import com.youlu.campus.entity.vote.UserVotePointEntity;
import com.youlu.campus.entity.vote.UserVotePointReceive;
import com.youlu.campus.entity.vote.VoteConstant;
import com.youlu.campus.entity.vote.dto.UserBaseInfoSaveDTO;
import com.youlu.campus.entity.vote.dto.VoteHomeSearchDTO;
import com.youlu.campus.entity.vote.dto.VotePointOpDTO;
import com.youlu.campus.service.ranking.UserActivityPointDetailService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.FindAndModifyOptions;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.query.Criteria;
import org.springframework.data.mongodb.core.query.Query;
import org.springframework.data.mongodb.core.query.Update;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Objects;

/**
 * @author : [dengbo]
 * @className : UserVoteService
 * @description : [用户投票服务]
 * @createTime : [2022/7/6 15:41]
 */
@Service
@Slf4j
public class UserVoteService {
    @Autowired
    private MongoTemplate mongoTemplate;
    @Autowired
    private ActivityVoteRedisService activityVoteRedisService;
    @Autowired
    private UserActivityPointDetailService userActivityPointDetailService;


    /**
     * 每天投票领取
     * @param dto
     * @param num
     */
    @Async
    public void perDayVoteReceive(VoteHomeSearchDTO dto, Integer num) {
        if (StringUtils.isBlank(dto.getUserId())) {
            return;
        }
        LocalDate date = LocalDate.now();
        boolean hasReceive = activityVoteRedisService.hasPerDayReceive(dto.getActivityId(), date, dto.getUserId());
        if (hasReceive) {
            return;
        }
        // 更新每天领取数据
        UserVotePointReceive receive = savePerDayVoteReceive(dto.getAppId(), dto.getActivityId(), dto.getUserId(), date, num);
        if (receive.getVersion() > 1) {
            log.info("perDayVoteReceive,has receive,activityId:{},userId:{}", dto.getActivityId(), dto.getUserId());
            return;
        }
        // 更新用户票数
        updateUserVoteInfo(dto, date, num);
        // 添加缓存每天领取数据
        activityVoteRedisService.addPerDayReceive(dto.getActivityId(), date, dto.getUserId(), num);
        // 添加每日领取明细
        addPerDayReceive(dto.getActivityId(), dto.getUserId());
    }

    /**
     * 添加每日领取记录
     * @param activityId
     * @param userId
     */
    public void addPerDayReceive(String activityId, String userId) {
        // 添加投票明细
        UserActivityPointDetail userActivityPointDetail = new UserActivityPointDetail();
        userActivityPointDetail.setAction("add");
        userActivityPointDetail.setActivityId(activityId);
        userActivityPointDetail.setBizDesc("每日赠送票数");
        userActivityPointDetail.setComments("每日赠送票数" + VoteConstant.PER_DAY_RECEIVE_NUM);
        userActivityPointDetail.setBizType("vote-receive");
        userActivityPointDetail.setOptDate(new Date());
        userActivityPointDetail.setPoints(VoteConstant.PER_DAY_RECEIVE_NUM);
        userActivityPointDetail.setUserId(userId);
        userActivityPointDetailService.save(userActivityPointDetail);
    }

    public UserVotePointReceive savePerDayVoteReceive(String appId, String activityId, String userId, LocalDate date, int num) {
        Update update = new Update();
        update.inc("version", 1);
        update.setOnInsert("num", num);
        update.setOnInsert("remainNum", num);
        update.setOnInsert("date", date);
        update.setOnInsert("activityId", activityId);
        update.setOnInsert("userId", userId);
        update.setOnInsert("appId", appId);
        FindAndModifyOptions options = new FindAndModifyOptions();
        options.returnNew(true);
        options.upsert(true);
        Query query = new Query(Criteria.where("activityId").is(activityId).and("date").is(date).and("userId").is(userId));
        UserVotePointReceive receive = mongoTemplate.findAndModify(query, update, options, UserVotePointReceive.class);
        return receive;
    }

    /**
     * 保存用户投票信息
     * @param dto
     * @return
     */
    @Async
    public UserVotePointEntity saveUserVoteInfo(UserBaseInfoSaveDTO dto) {
        Date date = new Date();
        Update votePointUpdate = new Update();
        votePointUpdate.set("nickName", dto.getUserPO().getNickName());
        votePointUpdate.set("avatarUrl", dto.getUserPO().getAvatarUrl());
        votePointUpdate.set("updatedTime", date);
        votePointUpdate.setOnInsert("activityId", dto.getActivityId());
        votePointUpdate.setOnInsert("userId", dto.getUserId());
        votePointUpdate.setOnInsert("appId", dto.getAppId());
        votePointUpdate.setOnInsert("openId", dto.getUserPO().getOpenId());
        votePointUpdate.setOnInsert("unionId", dto.getUserPO().getUnionId());
        votePointUpdate.setOnInsert("createdTime", date);
        FindAndModifyOptions voteOptions = new FindAndModifyOptions();
        if (StringUtils.isNotBlank(dto.getUserName())) {
            votePointUpdate.set("userName", dto.getUserName());
        }
        if (StringUtils.isNotBlank(dto.getMobile())) {
            votePointUpdate.set("mobile", dto.getMobile());
        }
        if (StringUtils.isNotBlank(dto.getUniversityId())) {
            votePointUpdate.set("universityId", dto.getUniversityId());
        }
        if (StringUtils.isNotBlank(dto.getUniversityName())) {
            votePointUpdate.set("universityName", dto.getUniversityName());
        }
        if (StringUtils.isNotBlank(dto.getUniversityCode())) {
            votePointUpdate.set("universityCode", dto.getUniversityCode());
        }
        voteOptions.returnNew(true);
        voteOptions.upsert(true);
        Query votePointQuery = new Query(Criteria.where("activityId").is(dto.getActivityId()).and("userId").is(dto.getUserId()));
        return mongoTemplate.findAndModify(votePointQuery, votePointUpdate, voteOptions, UserVotePointEntity.class);
    }

    /**
     * 更新用户投票信息
     * @param dto
     * @param date
     * @param num
     * @return
     */
    public UserVotePointEntity updateUserVoteInfo(VoteHomeSearchDTO dto, LocalDate date, Integer num) {
        //更新记录
        Update votePointUpdate = new Update();
        votePointUpdate.inc("totalNum", num);
        votePointUpdate.set("expireNum", num);
        votePointUpdate.set("lastDate", date);
        votePointUpdate.set("lastDateTime", LocalDateTime.now());
        votePointUpdate.set("nickName", dto.getUserPO().getNickName());
        votePointUpdate.set("avatarUrl", dto.getUserPO().getAvatarUrl());
        votePointUpdate.set("updatedTime", new Date());
        votePointUpdate.setOnInsert("createdTime", new Date());
        votePointUpdate.setOnInsert("permanentNum", 0);
        votePointUpdate.setOnInsert("totalVoteNum", 0);
        votePointUpdate.setOnInsert("inviteNum", 0);
        votePointUpdate.setOnInsert("activityId", dto.getActivityId());
        votePointUpdate.setOnInsert("openId", dto.getUserPO().getOpenId());
        votePointUpdate.setOnInsert("unionId", dto.getUserPO().getUnionId());
        votePointUpdate.setOnInsert("userId", dto.getUserId());
        votePointUpdate.setOnInsert("appId", dto.getAppId());
        FindAndModifyOptions voteOptions = new FindAndModifyOptions();
        voteOptions.returnNew(true);
        voteOptions.upsert(true);
        Query votePointQuery = new Query(Criteria.where("activityId").is(dto.getActivityId()).and("userId").is(dto.getUserId()));
        return mongoTemplate.findAndModify(votePointQuery, votePointUpdate, voteOptions, UserVotePointEntity.class);
    }


    /**
     * 增加/减少投票数量
     * @param dto
     * @return
     */
    public Pair<Boolean, String> updateVoteNum(VotePointOpDTO dto) {
        Query query = new Query(Criteria.where("activityId").is(dto.getActivityId()).and("userId").is(dto.getUserId()));
        boolean isAdd = dto.getNum() >= 0;
        Update up = new Update();
        if (!isAdd) {
            query.addCriteria(Criteria.where("totalNum").gte(-dto.getNum()));
            //减少过期票数
            up.inc("expireNum", dto.getNum());
            //增加投票数量
            up.inc("totalVoteNum", -dto.getNum());
        } else {
            //增加任务票数
            up.inc("permanentNum", dto.getNum());
        }
        if (dto.getInviteNum() != null) {
            up.inc("inviteNum", dto.getInviteNum());
        }
        //增加/减少总的票数
        up.inc("totalNum", dto.getNum());
        up.setOnInsert("activityId", dto.getActivityId());
        up.setOnInsert("userId", dto.getUserId());
        up.setOnInsert("createdTime", new Date());
        FindAndModifyOptions options = new FindAndModifyOptions();
        options.returnNew(true);
        UserVotePointEntity point = mongoTemplate.findAndModify(query, up, options, UserVotePointEntity.class);
        if (point == null) {
            log.warn("updateVoteNum userId:{} inc num:{} fail", dto.getUserId(), dto.getNum());
            return Pair.of(false, "当前可用票数不足");
        }
        log.info("updateVoteNum userId:{} inc num:{} success,total:{}", dto.getUserId(), dto.getNum(), point.getTotalNum());
        LocalDate date = LocalDate.now();
        // 如果当日领取了并且是扣减，并且数量大于0
        if (!isAdd && point.getExpireNum() >= 0 && point.getLastDate().isEqual(date)) {
            // 更新每次领取记录数据
            updateReceiveInfo(dto, date);
        }
        // 添加用户排行榜数据
        return Pair.of(true, null);
    }

    public UserVotePointEntity findUserVotePoint(String activityId, String userId) {
        Query votePointQuery = new Query(Criteria.where("activityId").is(activityId).and("userId").is(userId));
        return mongoTemplate.findOne(votePointQuery, UserVotePointEntity.class);
    }

    public Pair<Boolean, Integer> receiveUserVoteCa(String activityId, String userId) {
        Query query = new Query(Criteria.where("activityId").is(activityId).and("userId").is(userId));
        UserVotePointEntity userVotePointEntity = mongoTemplate.findOne(query, UserVotePointEntity.class);
        if (Objects.isNull(userVotePointEntity) || userVotePointEntity.getTotalVoteNum() < 3) {
            return Pair.of(false, 6001);
        }
        Update update = new Update();
        update.set("voteCaReceive", Boolean.TRUE);
        update.set("voteCaReceiveTime", new Date());
        UpdateResult result = mongoTemplate.updateFirst(query, update, UserVotePointEntity.class);
        return result.getModifiedCount() > 0 ? Pair.of(true, 0) : Pair.of(false, 500);
    }

    public boolean incVoteInviteNum(String activityId, String userId) {
        Query query = new Query(Criteria.where("activityId").is(activityId).and("userId").is(userId));
        Update update = new Update();
        update.inc("inviteNum", 1);
        UpdateResult result = mongoTemplate.updateFirst(query, update, UserVotePointEntity.class);
        return result.getModifiedCount() > 0;
    }

    public Pair<Boolean, Integer> receiveUserInViteCa(String activityId, String userId) {
        Query query = new Query(Criteria.where("activityId").is(activityId).and("userId").is(userId));
        UserVotePointEntity userVotePointEntity = mongoTemplate.findOne(query, UserVotePointEntity.class);
        if (Objects.isNull(userVotePointEntity) || userVotePointEntity.getInviteNum() <= 0) {
            return Pair.of(false, 6001);
        }
        Update update = new Update();
        update.set("inViteCaReceive", Boolean.TRUE);
        update.set("inViteCaReceiveTime", new Date());
        UpdateResult result = mongoTemplate.updateFirst(query, update, UserVotePointEntity.class);
        return result.getModifiedCount() > 0 ? Pair.of(true, 0) : Pair.of(false, 500);
    }

    public void updateReceiveInfo(VotePointOpDTO dto, LocalDate date) {
        long diffNum = activityVoteRedisService.getPerDayReceive(dto.getActivityId(), date, dto.getUserId());
        if (diffNum <= 0) {
            return;
        }
        // 扣减缓存
        activityVoteRedisService.incPerDayReceive(dto.getActivityId(), date, dto.getUserId(), dto.getNum());
        //只要扣减数量remainNum 数量<=0 表示已经扣减完成了
        Query query = new Query(Criteria.where("activityId").is(dto.getActivityId()).and("date").is(date).and("userId").is(dto.getUserId()));
        query.addCriteria(Criteria.where("remainNum").gt(0));
        Update update = new Update();
        update.inc("remainNum", dto.getNum());
        UserVotePointReceive receive = mongoTemplate.findAndModify(query, update, UserVotePointReceive.class);
        if (receive != null) {
            log.info("updateVoteNum userId:{} inc per day num success num:{}", dto.getUserId(), dto.getNum());
        }
        /*if (dto.getNum() >= -1) {
            query.addCriteria(Criteria.where("remainNum").gte(-dto.getNum()));
            Update update = new Update();
            update.inc("remainNum", dto.getNum());
            UserVotePointReceive receive = mongoTemplate.findAndModify(query, update, UserVotePointReceive.class);
            if (receive != null) {
                log.info("UserVoteService,updateVoteNum userId:{} inc per day num success num:{}", dto.getUserId(), dto.getNum());
            }
        } else {
            UserVotePointReceive receive = mongoTemplate.findOne(query, UserVotePointReceive.class);
            if (receive.getRemainNum() > 0) {
                Update update = new Update();
                if (receive.getRemainNum() >= -dto.getNum()) {
                    update.inc("remainNum", dto.getNum());
                } else {
                    update.inc("remainNum", 0);
                }
                UpdateResult result = mongoTemplate.updateFirst(query, update, UserVotePointReceive.class);
                if (result.getModifiedCount() > 0) {
                    log.info("UserVoteService,updateVoteNum userId:{} inc per day num success num:{}", dto.getUserId(), dto.getNum());
                }
            }
        }*/
    }
}
